package xyz.riceball.factory;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.riceball.beans.BeanDefinition;
import xyz.riceball.beans.BeanDefinitionRegistry;
import xyz.riceball.exception.BeanDefinitionRegistryException;
import xyz.riceball.annotation.Value;
import xyz.riceball.util.DataUtil;
import xyz.riceball.util.StringUtil;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <p>
 *
 * </p>
 *
 * @author xiaovcloud
 * @since 2020/06/29 19:25
 */
public class DefaultBeanFactory implements BeanFactory, BeanDefinitionRegistry, Closeable {

    private final Logger log = LoggerFactory.getLogger(this.getClass());
    /**
     * 用Map来存放bean定义信息
     */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

    /**
     * 用Map来存放创建的bean实例,这里只考虑单例bean
     */
    private final Map<String, Object> beanMap = new ConcurrentHashMap<>(256);

    public Map<String, BeanDefinition> getBeanDefinitionMap() {
        return this.beanDefinitionMap;
    }

    public Map<String, Object> getBeanMap() {
        return this.beanMap;
    }

    @Override
    public Object getBean(String beanName) throws Exception {
        return this.beanMap.get(beanName);
    }

    /**
     * 根据bean的名字获取bean实例,
     * 主要做的工作是创建bean实例和对bean实例进行初始化
     *
     * @param beanName bean的名字
     * @return 对象
     * @throws Exception 。
     */
    protected Object doGetBean(String beanName) throws Exception {
        //判断给入的bean名字不能为空
        Objects.requireNonNull(beanName, "beanName不能为空");
        //先从beanMap里面获取bean实例
        Object instance = beanMap.get(beanName);
        //如果beanMap里面已存在bean实例就直接返回,不需要走后面的流程了
        if (instance != null) {
            return instance;
        }
        //从beanDefinitionMap里面获取bean定义信息
        BeanDefinition bd = this.getBeanDefinition(beanName);
        //bean定义信息不能为空
        Objects.requireNonNull(bd, "beanDefinition不能为空");

        //获取bean的类型
        Class<?> type = bd.getBeanClass();
        if (type != null) {
            //如果bean的类型不为空,并且工厂方法名为空,说明是使用构造方法的方式来创建bean实例
            if (StringUtils.isBlank(bd.getFactoryMethodName())) {
                // 构造方法来构造对象
                instance = this.createInstanceByConstructor(bd);
            }
            //如果bean的类型不为空,并且工厂方法名不为空,说明是使用静态工厂方法的方式来创建bean实例
            else {
                // 静态工厂方法
                instance = this.createInstanceByStaticFactoryMethod(bd);
            }
        }
        //如果bean的类型为空,说明是使用工厂bean的方式来创建bean实例
        else {
            // 工厂bean方式来构造对象
            instance = this.createInstanceByFactoryBean(bd);
        }
        // 执行初始化方法
        this.doInit(bd, instance);
        beanMap.put(beanName, instance);
        return instance;
    }

    /**
     * 构造方法来构造对象 反射
     *
     * @param bd bean的定义信息
     * @return 对象
     * @throws InstantiationException    。
     * @throws IllegalAccessException    。
     * @throws InvocationTargetException 。
     * @throws NoSuchMethodException     。
     */
    private Object createInstanceByConstructor(BeanDefinition bd)
            throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        try {
            //拿到bean的类型,然后调用newInstance通过反射来创建bean实例
            return bd.getBeanClass().getConstructor().newInstance();
        } catch (SecurityException | NoSuchMethodException | InvocationTargetException e) {
            log.error("创建bean的实例异常,beanDefinition：" + bd, e);
            throw e;
        }
    }


    /**
     * 静态工厂方法 反射
     *
     * @param bd bean定义信息
     * @return 对象
     * @throws Exception 。
     */
    private Object createInstanceByStaticFactoryMethod(BeanDefinition bd) throws Exception {
        //拿到bean的类型
        Class<?> type = bd.getBeanClass();
        //通过静态工厂方法方法的名字getFactoryMethodName反射出bean的方法m创建bean实例
        Method m = type.getMethod(bd.getFactoryMethodName());
        return m.invoke(type);
    }

    /**
     * 工厂bean方式来构造对象 反射
     *
     * @param bd bean定义信息
     * @return 对象
     * @throws Exception 。
     */
    private Object createInstanceByFactoryBean(BeanDefinition bd) throws Exception {
        //通过bean定义信息中工厂bean的名字获取工厂bean的实例
        Object factoryBean = this.doGetBean(bd.getFactoryBeanName());
        //通过bean定义信息中工厂方法的名字反射出工厂bean的方法m创建bean实例
        Method m = factoryBean.getClass().getMethod(bd.getFactoryMethodName());
        return m.invoke(factoryBean);
    }

    /**
     * 执行初始化方法
     *
     * @param bd       bean定义信息
     * @param instance 对象
     * @throws Exception 。
     */
    private void doInit(BeanDefinition bd, Object instance) throws Exception {
        // 获取bean定义中的初始化方法,如果存在初始化方法就通过反射去执行初始化方法
        if (StringUtils.isNotBlank(bd.getInitMethodName())) {
            Method m = instance.getClass().getMethod(bd.getInitMethodName());
            m.invoke(instance);
        }
        Class<?> clazz = instance.getClass();
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            Value valueAnnotation = declaredField.getAnnotation(Value.class);
            if (valueAnnotation != null) {
                String value = valueAnnotation.value();
                String fieldName = declaredField.getName();
                String methodName = StringUtil.setFieldName(fieldName);
                Method method = clazz.getMethod(methodName, declaredField.getType());
                //完成数据类型转换
                Object val = DataUtil.convertValType(value, declaredField.getType());
                method.setAccessible(true);
                method.invoke(instance, val);
            }
        }
    }


    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionRegistryException {
        //判断给入的beanName和beanDefinition不能为空
        Objects.requireNonNull(beanName, "注册bean需要给入beanName");
        Objects.requireNonNull(beanDefinition, "注册bean需要给入beanDefinition");
        // 校验给入的bean是否合法
        if (!beanDefinition.validate()) {
            throw new BeanDefinitionRegistryException("名字为[" + beanName + "] 的bean定义不合法：" + beanDefinition);
        }
        //如果已存在bean定义就抛异常
        if (this.containsBeanDefinition(beanName)) {
            throw new BeanDefinitionRegistryException(
                    "名字为[" + beanName + "] 的bean定义已存在:" + this.getBeanDefinition(beanName));
        }
        //把bean定义放到Map里面
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        return this.beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return this.beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public void close() throws IOException {
        // 执行单例实例的销毁方法
        for (Map.Entry<String, BeanDefinition> e : this.beanDefinitionMap.entrySet()) {
            String beanName = e.getKey();
            BeanDefinition bd = e.getValue();

            //获取bean定义中的销毁方法,如果存在销毁方法就通过反射去执行销毁方法
            if (StringUtils.isNotBlank(bd.getDestroyMethodName())) {
                Object instance = this.beanMap.get(beanName);
                try {
                    Method m = instance.getClass().getMethod(bd.getDestroyMethodName());
                    m.invoke(instance);
                } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                        | InvocationTargetException e1) {
                    log.error("执行bean[" + beanName + "] " + bd + " 的 销毁方法异常！", e1);
                }
            }
        }
    }

    @Override
    public <T> T getBean(String beanName, Class<T> requiredType) throws Exception {
        Object instance = this.beanMap.get(beanName);
        if (instance != null && instance.getClass() == requiredType) {
            return (T) instance;
        }
        return null;
    }

    @Override
    public <T> T getBean(Class<T> requiredType) throws Exception {
        for (String beanName : this.beanMap.keySet()) {
            Object instance = this.beanMap.get(beanName);
            if (instance != null && instance.getClass() == requiredType) {
                return (T) instance;
            }
        }
        return null;
    }

    @Override
    public boolean containsBean(String name) throws Exception {
        return this.beanMap.containsKey(name);
    }
}
